package com.code4j.tree;

/*
 * Programming graphical user interfaces
 * Example: BinaryTreeView.java
 * Jarkko Leponiemi 2003
 */

import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JPanel;

/**
 * A class representing a graphical view of a binary tree.
 */
public class BinaryTreeView extends JPanel implements Observer {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	// the binary tree
	/**
	 * model
	 */
	private BinaryTree tree = null;
	
	// the node location of the tree
	private HashMap<BinaryTree.Node, Rectangle> nodeLocations = null;
	// the sizes of the subtrees
	private HashMap<BinaryTree.Node, Dimension> subtreeSizes = null;
	// do we need to calculate locations
	//private boolean dirty = true;
	// space between nodes
	private int parent2child = 20, child2child = 30;

	// helpers
	private Dimension empty = new Dimension(0, 0);
	private FontMetrics fm = null;

	public BinaryTreeView(/*BinaryTree tree*/) {
		//this.tree = tree;
		nodeLocations = new HashMap<BinaryTree.Node, Rectangle>();
		subtreeSizes = new HashMap<BinaryTree.Node, Dimension>();
//		registerKeyboardAction(this, "add", KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), WHEN_IN_FOCUSED_WINDOW);
//		registerKeyboardAction(this, "random", KeyStroke.getKeyStroke(KeyEvent.VK_R, 0), WHEN_IN_FOCUSED_WINDOW);
//		registerKeyboardAction(this, "print", KeyStroke.getKeyStroke(KeyEvent.VK_P, 0), WHEN_IN_FOCUSED_WINDOW);
	}

	
	
	// event handler for pressing "A"
//	public void actionPerformed(ActionEvent e) {
//		if (e.getActionCommand().equals("add")) {
//			String c = JOptionPane.showInputDialog("Add a new node:");
//			if (c != null && !"".equals(c)) {
//				//tree.addNode(c);
//				tree.insert(Integer.parseInt(c));
//				dirty = true;
//				repaint();
//			}
//		} else if (e.getActionCommand().equals("random")) {
//			TreeBuilder builder = new TreeBuilder();
//			tree = builder.getTree();
//			dirty = true;
//			repaint();
//
//		} else if (e.getActionCommand().equals("print")) {
//			tree.preOrder();
//
//		}
//	}


	// calculate node locations
	private void calculateLocations() {
		nodeLocations.clear();
		subtreeSizes.clear();
		BinaryTree.Node root = tree.getRoot();
		if (root != null) {
			calculateSubtreeSize(root);
			calculateLocation(root, Integer.MAX_VALUE, Integer.MAX_VALUE, 0);
		}
	}

	// calculate the size of a subtree rooted at n
	private Dimension calculateSubtreeSize(BinaryTree.Node n) {
		if (n == null)
			return new Dimension(0, 0);
		//String s = n.getData() + "";
		Dimension ld = calculateSubtreeSize(n.getLeft());
		Dimension rd = calculateSubtreeSize(n.getRight());
		int h = fm.getHeight() + parent2child + Math.max(ld.height, rd.height);
		int w = ld.width + child2child + rd.width;
		Dimension d = new Dimension(w, h);
		subtreeSizes.put(n, d);
		return d;
	}

	// calculate the location of the nodes in the subtree rooted at n
	private void calculateLocation(BinaryTree.Node n, int left, int right, int top) {
		if (n == null)
			return;
		Dimension ld = (Dimension) subtreeSizes.get(n.getLeft());
		if (ld == null)
			ld = empty;
		Dimension rd = (Dimension) subtreeSizes.get(n.getRight());
		if (rd == null)
			rd = empty;
		int center = 0;
		if (right != Integer.MAX_VALUE)
			center = right - rd.width - child2child / 2;
		else if (left != Integer.MAX_VALUE)
			center = left + ld.width + child2child / 2;
		int width = fm.stringWidth(n.getData() + "");
		Rectangle r = new Rectangle(center - width / 2 - 3, top, width + 6, fm.getHeight());
		nodeLocations.put(n, r);
		calculateLocation(n.getLeft(), Integer.MAX_VALUE, center - child2child / 2, top + fm.getHeight() + parent2child);
		calculateLocation(n.getRight(), center + child2child / 2, Integer.MAX_VALUE, top + fm.getHeight() + parent2child);
	}

	// draw the tree using the pre-calculated locations
	private void drawTree(Graphics2D g, BinaryTree.Node n, int px, int py, int yoffs) {
		if (n == null)
			return;
		Rectangle r = (Rectangle) nodeLocations.get(n);
		g.draw(r);
		//Dimension d = subtreeSizes.get(n);
		//g.drawRect(r.x, r.y, d.width, d.height);
		g.drawString(n.getData() + "", r.x + 3, r.y + yoffs);
		if (px != Integer.MAX_VALUE)
			g.drawLine(px, py, r.x + r.width / 2, r.y);
		drawTree(g, n.getLeft(), r.x + r.width / 2, r.y + r.height, yoffs);
		drawTree(g, n.getRight(), r.x + r.width / 2, r.y + r.height, yoffs);
	}

	public void paint(Graphics g) {
		super.paint(g);
		if(tree == null) return;
		fm = g.getFontMetrics();
		// if node locations not calculated
//		if (dirty) {
			calculateLocations();
//			dirty = false;
//		}
		Graphics2D g2d = (Graphics2D) g;
		g2d.translate(getWidth() / 2, parent2child);
		//System.out.println(fm.getLeading()+","+fm.getAscent()+","+fm.getDescent()+","+fm.getHeight());
		drawTree(g2d, tree.getRoot(), Integer.MAX_VALUE, Integer.MAX_VALUE, fm.getLeading() + fm.getAscent());
		fm = null;
	}

	@Override
	public void update(Observable o, Object arg) {
		if(arg instanceof BinaryTree){
			tree = (BinaryTree) arg;
			repaint();
		}
	}

}
